<?php
namespace app\process;
use app\job\CompareFeatureSub;
use app\model\Faces;
use app\model\FacesFeature;
use fast\Http;
use Swoole\Coroutine as Co;
use Swoole\Server;
use think\facade\Cache;
use think\facade\Log;
use think\facade\Queue;

class CalFeature
{
    var $pipe;

    /*public function __construct($pipe)
    {
        $this->pipe = $pipe;
    }*/
    public function cal_max_feature($tasks,$feature,$score,$log_id=0) {
        Log::debug($log_id."计算feature最大值单进程>>开始");
        $max=$score;
        $result=null;
        foreach ($tasks as $feature_dst){
            $temp=$this->compare_feature_str($feature,$feature_dst['feature']);
            Log::debug($log_id."比较feature:".$feature_dst['id'].'::'.$temp);
            if($max<$temp){
                $max=$temp;
                $feature_dst['score']=$max;
                //$feature_dst['photo_id']=$feature_dst['id'];
                $result=$feature_dst;
            }
        }
        Log::debug($log_id."计算feature最大值单进程>>获取最大值：".json_encode($result));
        //$this->pipe->write($result);
        if($result){
            $key="feature_".$feature;
            $data=Cache::get($key);
            Log::debug($log_id."计算feature最大值单进程>>获取之前最大值：".json_encode($data));
            if($data){
                //$data=json_decode($data,true);
                if($data['score']<$result['score']){
                    Cache::set($key,$result);
                }
            }else{
                Cache::set($key,$result);
            }
        }

    }

    /*public function coRun1($tasks,$feature,$score=0){
        Log::info(">>>协和号启动:");
        $results=[];
        for ($i = 0; $i < 20; $i++) {
            Co::create(function () use ($i,&$results) {
                // 调用同一个函数，传入不同的参数
                $id = Co::getuid();
                Log::info("协和号启动:".$i);
                $results[]=$id;
            });

        }
        Log::info("协和号结束1:");
        // 等待所有协程任务完成
       //\Swoole\Event::wait();
        while (count($results) >= 20) {
            \Swoole\Coroutine::yield();
        }
        Log::info("协和号结束2:");
        foreach ($results as $result) {
            Log::info("协和号>>:".json_encode($result));
        }
        Log::info("协和号结束3:".json_encode($results));
        return $results;
    }*/

    public function coRun($tasks,$feature,$score=0){
        Log::info(">>>协程启动核心数:".swoole_cpu_num());
        $configs=config('capture.Local');
        if(!$score){
            $score=$configs[''];
        }
        Log::info("协程处理总任务数：".count($tasks));
        $core=config('capture.sub_process');
        //$core=80;
        $size=count($tasks)/$core;
        $tasks=array_chunk($tasks,$size);

        Log::info("协程分组处理任务数：".count($tasks));
        $chan = new Swoole\Coroutine\Channel();
        foreach ($tasks as $key=>$task) {
            Swoole\Coroutine\go(function () use ($chan,$task,$score,$feature,$key) {
                //$id=Co::getuid();
                $result = $this->compare_task($task, $score, $feature,$key);
                $chan->push($result);
            });
        }


        // 等待所有协程任务完成

        $res=[];
        for ($i=0;$i<$core;$i++){
            $res=array_merge($res,$chan->pop());
        }
        return $res;
    }

    public function get_feature($path,$log_id=0,$fix=true) {
        $log_str=$log_id."获取图片feature:";
        Log::debug($log_str.$path."，运行模式：".PHP_SAPI);
        $urls=config('capture.Local.feture_urls');
        $url=$urls[rand(0,count($urls)-1)];
        Log::debug($log_str.$path.'  '.$url);
        if(PHP_SAPI=='cli1'){
            Log::info($log_str.'CLI模式请求');
            $chan = new Co\Channel();
            Co::create(function () use ($url,$path,$chan,$log_id) {
                $parts = parse_url($url);
                $client = new Co\Http\Client($parts['host'],isset($parts['port'])?$parts['port']:80);
                $client->set(['timeout' => 10]); // 设置超时时间

                //$client->setHeaders(['Content-Type' => 'multipart/form-data']);
                $client->setMethod('POST');
                $client->addFile($path, 'file', basename($path));
                $client->execute($parts['path'].'?'.$parts['query']);
                Log::debug($log_id.'result:'.json_encode($client));
                if ($client->statusCode == 200) {
                    // 成功处理响应
                    //return $client->body;
                    $chan->push($client->body);
                    // ...
                } else {
                    // 处理错误
                    echo "error";
                }
                $client->close(); // 关闭连接
            });
            $res = json_decode($chan->pop(),true);
        }else{
            $fp=new \CURLFile($path);
            $data['file']=$fp;
            $curl=[CURLOPT_HTTPHEADER => ['Content-Type:multipart/form-data']];
            $res=Http::post_res($url,$data,$curl);
            if($res['ret']) {
                $res = json_decode($res['msg'], true);
            }
        }

        Log::debug($log_id."调用接口获取feature:".json_encode($res));
        if($res&&isset($res['Code'])&&$res['Code']==200){
            return $res['Data']['Items'][0]['Feature'];
        }else{
            if($fix){
                Log::debug($log_id."未获取feature，使用fix方法重新获取");
                return $this->get_feature_fix($path,$log_id);
            }
        }
        return '';
    }

    /**
     * 特征值比较，返回比较数值
     * @param $feature_src
     * @param $feature_dst
     * @return float|int
     */
    public function compare_feature_str($feature_src, $feature_dst) {
        if(!$feature_src||!$feature_dst){
            return 0;
        }
        // Base64解码
        $feature_src_dec = base64_decode($feature_src);
        $feature_dst_dec = base64_decode($feature_dst);

        // 计算二进制数据长度除以4（因为一个float占用4个字节）
        $feature_src_dec_size = intval(strlen($feature_src_dec) / 4);
        $feature_dst_dec_size = intval(strlen($feature_dst_dec) / 4);

        // 解包二进制数据为浮点数数组
        $src_feature_arr = unpack('f*', substr($feature_src_dec, 8));
        $dst_feature_arr = unpack('f*', substr($feature_dst_dec, 8));

        // 由于unpack返回的是关联数组，我们需要将其转换为数值数组
        $src_feature_arr = array_values($src_feature_arr);
        $dst_feature_arr = array_values($dst_feature_arr);

        // 计算点积
        $feature_dot_result = 0.0;
        for ($i = 0; $i < count($src_feature_arr); $i++) {
            $feature_dot_result += $src_feature_arr[$i] * $dst_feature_arr[$i];
        }

        // 计算模长
        $feature_x_result = 0.0;
        foreach ($src_feature_arr as $value) {
            $feature_x_result += $value * $value;
        }
        $feature_x_result = sqrt($feature_x_result);
        $feature_y_result = 0.0;
        foreach ($dst_feature_arr as $value) {
            $feature_y_result += $value * $value;
        }
        $feature_y_result = sqrt($feature_y_result);
        if(!($feature_x_result * $feature_y_result)){
            Log::debug("对比余弦：0");
            return 0;
        }
        // 计算余弦相似度
        $feature_cosine = $feature_dot_result / ($feature_x_result * $feature_y_result);

        // 输出结果
        //echo "cosine: " . $feature_cosine . ", score: " . $feature_cosine . "\n";
        Log::debug("对比结果：".$feature_cosine);
        return round($feature_cosine*100,2);
    }

    public function coTask(array $tasks, $feature, $score,$log_id=0) {
        $res=[];
        Log::info($log_id.">>>协程启动核心数:".swoole_cpu_num());
        $configs=config('capture.Local');
        if(!$score){
            $score=$configs['max_feture'];
        }
        Log::info($log_id."进程处理总任务数：".count($tasks));
        $core=config('capture.sub_process');
        //$core=80;
        $size=count($tasks)/$core;
        $tasks=array_chunk($tasks,$size);

        Log::info($log_id."进程分组处理任务数：".count($tasks));
        $_server=new Server();
        foreach ($tasks as $key => $task) {
            $_server->task(FeatureTask::pack([
                'task' => $task,
                'score' => $score,
                'feature' => $feature,
                'key' => $key,
            ]), function ($taskId, $data, $result) use ($key,$res,$core) {
                for ($i=0;$i<$core;$i++){
                    $res=array_merge($res,$result);
                }
            });
        }
        return $res;
    }

    public function compare_task($tasks, $score, $feature,$id,$main_id,$sub_id=-1,$photo_id=0) {
        $results=[];
        $time=microtime(true);
        $core=config('capture.sub_process');
        if(count($tasks)>1000&&$sub_id<0){
            Log::info($id."主线程".$main_id."数据过多".count($tasks));
            $nums=ceil(count($tasks)/$core);
            $tasks=array_chunk($tasks,$nums);
            for ($i=0;$i<$core;$i++) {
                $content=json_encode([
                    'task' => $tasks[$i],
                    'score' => $score,
                    'feature' => $feature,
                    'log_id'=>$id,
                    'main_id'=>$main_id,
                    'photo_id'=>$photo_id,
                    'sub_id'=>$i
                ]);
                $job_data = [];
                $job_data["ts"] = time();
                $job_data["bizid"] = uniqid();
                $job_data["params"] =$content;
                $is_pushed=Queue::push(CompareFeatureSub::class,$job_data,'sub_compare_feature',false);
                //Log::info($id.":第".$i."次投递子任务：".$is_pushed);
            }
            return true;
        }
        foreach ($tasks as $task) {
            // 调用同一个函数，传入不同的参数
            $temp=0;
            if(!isset($task['feature'])||!$task['feature']){
                Log::debug("协程".$id."无feature:".json_encode($task));
            }else{
                try {
                    $temp=$this->compare_feature_str($feature,$task['feature']);
                    //Log::info("协程".$id."比较feature:".$task['id'].'::'.$temp);
                }catch (\Exception $e){
                    Log::error($e->getMessage());
                    Log::error("协程".$id."比较错误:".json_encode($task));
                }

            }
            if($score<$temp){
                if($photo_id){
                    Faces::where('id',$task['id'])->save(['photo_id'=>$photo_id]);
                }else{
                    unset($task['feature']);
                    unset($task['feature_status']);
                    $task=Faces::where('id',$task['id'])->column('id,create_time,device_id,device_name,file_path');
                    $task=$task[0];
                    $task['score']=$temp;
                    $results[]=$task;
                }

            }
        }
        Log::info($id."主线程".$main_id."子线程".$sub_id."处理任务数：".count($tasks).',处理时间：'.(microtime(true)-$time));
        $key=$id.'_'.$main_id;
        if($sub_id>-1&&!$photo_id){
            //Log::info($id."写入主线程：".$main_id.'中的子线程：'.$sub_id);
            Cache::set($key.'_'.$sub_id,$results,60);
            $full=true;
            for ($i=0;$i<$core;$i++){
                if(!Cache::has($key.'_'.$i)){
                    $full=false;
                    break;
                }else{
                    $results=array_merge(Cache::get($key.'_'.$i),$results);
                }
            }
            if($full){
                //Log::info($id."子线程计算完毕,写入主线程：");
                Cache::set($key,$results,60);
            }
            return true;
        }
        Log::info($id."写入主线程：".$main_id);
        Cache::set($key,$results,60);
        /*if(Cache::has($id)){
            $res=Cache::get($id);
            //Log::info($id."主线程".$main_id."子线程".$sub_id."缓存中的数据：");
            Log::info($id."主线程".$main_id.'数据总数：'.count($res));
            //Log::info($id."主线程".$main_id."子线程".$sub_id."缓存中的数据：");
            if(!isset($res[$main_id])){
                $res[$main_id]=[];
            }
            $res[$main_id]=array_merge($res[$main_id],$results);
            Cache::set($id,$res,3600);
        }else{
            Log::info($id."主线程".$main_id."子线程".$sub_id."缓存中的数据：0");
            $res[$main_id]=$results;
            Cache::set($id,$res,3600);
        }*/
        //return $results;
    }

    public function singleTask($params) {
        $params=json_decode($params,true);
        $log_id=$params['log_id'];
        $temp=$params;
        unset($temp['feature']);
        //Log::info($log_id."处理的任务数据：".json_encode($temp));
        $tasks=FacesFeature::whereBetweenTime('create_time',date('Y-m-d H:i:s',$params['start_time']),date('Y-m-d H:i:s',$params['end_time']))->select()->toArray();
        if($params['photo_id']){
            $avaTasks=Faces::where('photo_id','')->column('id');
            for ($i=0;$i<count($tasks);$i++){
                if(!in_array($tasks[$i]['id'],$avaTasks)){
                    unset($tasks[$i]);
                }
            }
        }
        $score=$params['score'];
        $feature=$params['feature'];
        $main_id=$params['main_id'];
        $this->compare_task($tasks, $score, $feature,$log_id,$main_id,-1,$params['photo_id']);
    }

    /**
     * 直接计算feature失败，对图片处理后重新计算
     * @param string $photo_path
     * @param string $log_id
     * @return void
     */
    public function get_feature_fix(string $photo_path, string $log_id) {
        Log::debug($log_id."使用fix方法重新获取");
        $file=str_replace(".jpg","_tmp.jpg",$photo_path);
        // 保存图片为JPEG格式
        //imagejpeg($sourceImage,$file , $quality);

        // 释放内存
        //imagedestroy($sourceImage);
        $image = \think\Image::open($photo_path);
        $width = $image->width();
        $height=$image->height();
        Log::info("file:".$file);
        if($width>400){
            $image->thumb(400, 400*$height/$width)->save($file);
        }else{
            $file=$photo_path;
        }
        $file_jpeg=str_replace(".jpg",".jpeg",$file);
        $sourceImage = imagecreatefromjpeg($file);
        // 保存图片为JPEG格式
        imagejpeg($sourceImage, $file_jpeg, 100);

        // 释放内存
        imagedestroy($sourceImage);
        Log::debug($log_id."重新调用get_feature方法");
        return $this->get_feature($file_jpeg,$log_id,false);
    }

    private function compare_task1($task, $score, $feature) {
        return Co::create(function () use ($task, $score, $feature) {
            // 调用同一个函数，传入不同的参数
            $id = Co::getuid();
            Log::info("协和号启动:".$id);
            $temp=0;
            if(!isset($task['feature'])){
                Log::info("协程".$id."无feature:".json_encode($task));
            }else{
                $temp=$this->compare_feature_str($feature,$task['feature']);
                Log::info("协程".$id."比较feature:".$task['id'].'::'.$temp);
            }
            if($score<$temp){
                $results[]=$task;
            }
            return $results;
        });
    }
}